#include "fxcc/core/graph/opengl3/font/FontLib.h"

fxcc::graph::opengl3::font::FontLib::FontLib(const std::string &path)
    :m_Path(path)
{
    m_Available = Create();
}

bool fxcc::graph::opengl3::font::FontLib::Create()
{
    if (FT_Init_FreeType(&m_Lib))
    {
        ztclog::info("cannot init freetype \n");
        return false;
    }
    if (FT_New_Face(m_Lib, m_Path.c_str(), 0, &m_Face))
    {
        ztclog::info("cannot freetype new face \n");
        return false;
    }
    m_Family = m_Face->family_name;

    InitResources();
    return true;
}
void fxcc::graph::opengl3::font::FontLib::DoneFace()
{
    FT_Done_Face(m_Face);

}

void fxcc::graph::opengl3::font::FontLib::DoneFreetype()
{    FT_Done_FreeType(m_Lib);

}

void fxcc::graph::opengl3::font::FontLib::Done()
{
    DoneFace();
    DoneFreetype();
}

bool fxcc::graph::opengl3::font::FontLib::HasWChar(const wchar_t &c,int fontSize)
{
    Character::Desc chDesc{ fontSize,c };

    if (m_CharMap.find(chDesc) == m_CharMap.end())
    {
        return false;
    }
    return true;
}

bool fxcc::graph::opengl3::font::FontLib::InitResources()
{
    auto& geoData = m_FontGeoData;

    geoData.Resize32(4, 6);

    geoData.indices32 =
    {
        0, 1, 2,
        0, 2, 3
    };
    geoData.vertices =
    {
        glm::vec3(0.f, 0.0f, 0.0f),
        glm::vec3(0.f, 0.0f, 0.0f),
        glm::vec3(0.f, 0.0f, 0.0f),
        glm::vec3(0.f, 0.0f, 0.0f),
    };
    geoData.normals =
    {
        glm::vec3(0.f, 0.0f, 1.0f),
        glm::vec3(0.f, 0.0f, 1.0f),
        glm::vec3(0.f, 0.0f, 1.0f),
        glm::vec3(0.f, 0.0f, 1.0f),
    };
    geoData.texcoords =
    {
         glm::vec2(0.0, 0.0),
         glm::vec2(0.0, 1.0),
         glm::vec2(1.0, 1.0),
         glm::vec2(1.0, 0.0),
    };
    m_FontMesh = std::make_shared<fxcc::graph::opengl3::MeshJack>(geoData);

    return true;
}

bool fxcc::graph::opengl3::font::FontLib::LoadWstring(const std::wstring& str, int fontSize)
{
    for (const wchar_t& ch : str)
    {
        if (!LoadWChar(ch, fontSize))
        {
            ztclog::debug("cannot load %c", ch);
            return false;
        }
    }
    return true;
};

void fxcc::graph::opengl3::font::FontLib::RenderTextMesh(const TextLine& textLine)
{

    const std::wstring& text = UTF8ToWString(textLine.m_Text);
    int fontSize = textLine.m_FontSize;
    float x = textLine.m_Pos.x;
    float y = textLine.m_Pos.y;
    float scale = 1.0f;

    glm::vec2 startPos = textLine.m_Pos;

    auto startPosIndex = startPos;

    bool m_OverLimit = false;

    auto& vertexBuffer = m_FontMesh->m_VertexBuffers[0];

    for (int index = 0; index < text.size(); index++)
    {
        const wchar_t& c = text[index];

        if (!HasWChar(c, fontSize))
        {
            LoadWChar(c, fontSize);
        }

        font::Character::Desc desc(fontSize, c);
        const auto& ch = m_CharMap[desc];

        if (ch->Avail())
        {
            ch->BindTexture(0);
            m_FontMesh->Bind();
            vertexBuffer.Bind();
            vertexBuffer.BufferData<glm::vec3>(ch->NewPoss(startPosIndex));
            m_FontMesh->DrawElementsAuto();

            float charAdvance = (ch->m_Advance.x >> 6);
            startPosIndex.x += charAdvance;

            if (textLine.m_UseLimit)
            {
                bool NextCharOverWidth = (startPosIndex.x - startPos.x > textLine.m_LimitSize.x);

                // get the next word width
                bool NextWordOverWidth = false;
                if (c == L' ')
                {
                    float wordWidth = 0;
                    int tempIndex = index + 1;

                    // util i get the end i calculate the width
                    while (tempIndex < text.size() && text[tempIndex] != L' ')
                    {
                        font::Character::Desc nextDesc(fontSize, text[tempIndex]);
                        if (!HasWChar(text[tempIndex], fontSize))
                        {
                            LoadWChar(text[tempIndex], fontSize);
                        }
                        const auto& nextChar = m_CharMap[nextDesc];
                        if (nextChar->Avail())
                        {
                            wordWidth += (nextChar->m_Advance.x >> 6);
                        }
                        tempIndex++;
                    }

                    NextWordOverWidth = (startPosIndex.x + wordWidth - startPos.x > textLine.m_LimitSize.x);
                }

                if (NextWordOverWidth)
                {
                    if (startPos.y + textLine.m_FontSize - startPosIndex.y > textLine.m_LimitSize.y)
                    {
                        m_OverLimit = true;
                        break;
                    }
                }

                bool shouldNewLine = NextCharOverWidth || NextWordOverWidth;

                if (shouldNewLine)
                {
                    startPosIndex.x = startPos.x;
                    startPosIndex.y -= textLine.m_FontSize;
                }
               
            }
        }
    }
    if (m_OverLimit)
    {
        for (int index = 0; index < 3; index++)
        {
            const wchar_t& c = L'.';
         
            RenderWChar(c, fontSize, startPosIndex);
        }
    }

    fxcc::graph::opengl3::Texture2DJack::UnBind();
}

bool fxcc::graph::opengl3::font::FontLib::RenderWChar(const wchar_t& c, int fontSize,  glm::vec2& startPosIndex)
{


    if (!HasWChar(c, fontSize))
    {
        LoadWChar(c, fontSize);
    }

    font::Character::Desc desc(fontSize, c);
    const auto& ch = m_CharMap[desc];

    auto& vertexBuffer = m_FontMesh->m_VertexBuffers[0];

    if (ch->Avail())
    {
        ch->BindTexture(0);
        m_FontMesh->Bind();
        vertexBuffer.Bind();
        vertexBuffer.BufferData<glm::vec3>(ch->NewPoss(startPosIndex));
        m_FontMesh->DrawElementsAuto();

        float charAdvance = (ch->m_Advance.x >> 6);
        startPosIndex.x += charAdvance;
    }
    return ch->Avail();
}

bool fxcc::graph::opengl3::font::FontLib::LoadWChar(const wchar_t &c, int fontSize)
{
    FT_Set_Pixel_Sizes(m_Face, 0, fontSize);

    // Load character glyph
    FT_UInt glyph_index = FT_Get_Char_Index(m_Face, c);
    if (FT_Load_Glyph(m_Face, glyph_index, FT_LOAD_RENDER))
    {
        return false;
    }

    // fxcc::graph::opengl3::Image desc;
    fxcc::graph::common::ImageMipmap::MipMapData image;

    image.m_HDR = false;
    image.m_Width = m_Face->glyph->bitmap.width;
    image.m_Height = m_Face->glyph->bitmap.rows;
    image.m_NrComponent = 1;
    image.AllocUINT8(m_Face->glyph->bitmap.buffer);

    fxcc::graph::common::Texture2DJack::Desc texDesc(image);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    std::shared_ptr<fxcc::graph::opengl3::Texture2DJack> tex = std::make_shared<fxcc::graph::opengl3::Texture2DJack>(texDesc);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

    auto character = std::make_shared<Character>();

    character->m_Texture = tex;
    character->m_Size = glm::ivec2(m_Face->glyph->bitmap.width, m_Face->glyph->bitmap.rows);
    character->m_Bearing = glm::ivec2(m_Face->glyph->bitmap_left, m_Face->glyph->bitmap_top);
    character->m_Advance = glm::ivec2(m_Face->glyph->advance.x, m_Face->glyph->advance.y);

    Character::Desc chDesc;
    chDesc.m_FontSize = fontSize;
    chDesc.m_WChar = c;
    m_CharMap.insert({ chDesc, character });
    
    return true;
}



fxcc::graph::opengl3::font::FontLib::~FontLib()
{
    Done();
}